﻿//*****************************************************************************
// (C) Copyright 2010 Tiago Pascoal
// This code is licensed under the Microsoft Public License (Ms-PL) 
// [http://www.microsoft.com/opensource/licenses.mspx#Ms-PL].
//
//*****************************************************************************
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using Microsoft.TeamFoundation.WorkItemTracking.Controls;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using System.Diagnostics;


namespace TFhisto.UI.Controls.QueryEditorControl
{
    public partial class QueryEditorControl : UserControl
    {
        private WorkItemStore _store;
        private string _teamProjectName;
        private DisplayFieldCollection _displayFields;
        private SortFieldList _sortFieldList;
        ImageList _toolbarImageList;

        public delegate void QueryRunRequestedHandler(object sender, QueryRunEventArgs e);

        public event QueryRunRequestedHandler QueryRunRequested;

        public QueryEditorControl(Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore store, string teamProjectName,string wiql)
        {
            this._store = store;
            this._teamProjectName = teamProjectName;

            InitializeComponent();
            InitializeImageLists();
            InitializeToolStripImages();

            queryBuilderControl.Initialize(_store, _teamProjectName);
            queryBuilderControl.MaximumPreferredHeight = queryBuilderGroupBox.Height - 5;
            
            InitializeQuery(wiql,teamProjectName);

            InitalizeQueryBuilderControlEvents();           

            UpdateToolStripButtons();
        }

        /// <summary>
        /// Initializes the query builder control events that are responsable to reacting to the context menu
        /// invocatiom, and to keep the toolstrip state updated when the clause focus has changed
        /// </summary>
        private void InitalizeQueryBuilderControlEvents()
        {
            queryBuilderControl.ClauseFocusChanged += new EventHandler(queryBuilderControl_ClauseFocusChanged);

            queryBuilderControl.ContextMenu = GenerateContextMenu();
            queryBuilderControl.ContextMenuPopup += new MouseEventHandler(queryBuilderControl_ContextMenuPopup);          
        }

        /// <summary>
        /// Sets the initial query editor data.
        /// 
        /// If no wiql query is passed then by default we initialize the editor with a default query
        /// otherwise we initialize the query control with the query that was passed
        /// 
        /// </summary>
        /// <param name="wiql">The query to initialize the grid with. If null we issue the default query ourselves</param>
        /// <param name="teamProjectName">the current team project name</param>
        private void InitializeQuery(string wiql, string teamProjectName)
        {
            if (wiql == null)
            {
                SetDefaultQuery(teamProjectName);
            }
            else
            {
                SetQuery(wiql, _store, teamProjectName);
            }
        }

        private void InitializeImageLists()
        {
            _toolbarImageList = GetToolBarIconsImageList();
        }

        /// <summary>
        /// Initializes the images for the buttons on the toolstrip. Sets the images for the
        /// buttons so the toolbar is visually identical to the TE query editor that the user is 
        /// familiar with
        /// </summary>
        private void InitializeToolStripImages()
        {
            queryBuilberToolStrip.ImageList = _toolbarImageList;

            toolStripDeleteClauseButton.ImageIndex = 12;
            toolStripDeleteClauseButton.DisplayStyle = ToolStripItemDisplayStyle.ImageAndText;
            toolStripGroupClauseButton.ImageIndex = 13;
            toolStripGroupClauseButton.DisplayStyle = ToolStripItemDisplayStyle.ImageAndText;
            toolStripUngroupClauseButton.ImageIndex = 14;
            toolStripUngroupClauseButton.DisplayStyle = ToolStripItemDisplayStyle.ImageAndText;
            toolStripColumnOptionsButton.ImageIndex = 27;
            toolStripColumnOptionsButton.DisplayStyle = ToolStripItemDisplayStyle.ImageAndText;

            toolStripRunButton.ImageIndex = 45;
            toolStripRunButton.DisplayStyle = ToolStripItemDisplayStyle.ImageAndText;
        }

        /// <summary>
        /// Obtains the images list of the toolbar icons.
        /// </summary>
        /// <returns></returns>
        private ImageList GetToolBarIconsImageList()
        {
            return GetImageFromResourcesStringList("ToolbarIcons");
        }

        /// <summary>
        /// Extracts an imagem list from the control resources string located in 
        /// Microsoft.TeamFoundation.WorkItemTracking.Controls assembly
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        private static ImageList GetImageFromResourcesStringList(string name)
        {
            Bitmap fieldChooserImages;
            ImageList imageList;

            fieldChooserImages = (System.Drawing.Bitmap)ResourceHelper.GetObjectFromResource("Microsoft.TeamFoundation.WorkItemTracking.Controls", "ControlResourceStrings", name);

            imageList = new ImageList();
            imageList.ImageSize = new Size(16, 16);
            imageList.TransparentColor = Color.Magenta;
            imageList.Images.AddStrip(fieldChooserImages);

            return imageList;
        }

        /// <summary>
        /// Initializes the query grid with an existing query.
        /// </summary>
        /// <param name="wiql"></param>
        /// <param name="store"></param>
        /// <param name="teamProjectName"></param>
        private void SetQuery(string wiql, WorkItemStore store,string teamProjectName)
        {
            Dictionary<string, string> ctx;
            Query query;

            ctx = GetWIQLContext(teamProjectName);

            // load the query so it is parsed and we can easily determine it's type, get the sort fields and the projection list
            query = new Query(store, wiql, ctx); 

            _displayFields = TransformListIntoCollection(query.DisplayFieldList);
            _sortFieldList = query.SortFieldList;

            if (query.IsLinkQuery)
            {
                SetQueryType(QueryType.OneHop);
            }
            else if (query.IsTreeQuery)
            {
                SetQueryType(QueryType.Tree);
            }
            else
            {
                SetQueryType(QueryType.List);
            }

            // we use the original WIQL on purpose. since query.QueryString macros have been expanded and replaced with the actual value. 
            // Sowe we use the original query to preserve the macros
            queryBuilderControl.SetWiql(wiql); 
        }

        private static Dictionary<string, string> GetWIQLContext(string teamProjectName)
        {
            Dictionary<string, string> ctx;
            ctx = new Dictionary<string, string>();
            ctx.Add("project", teamProjectName);
            ctx.Add("me", "dummy"); // we only need a dummy value so the parser doesn't complain in case the query contains @Me macro
            return ctx;
        }

        /// <summary>
        /// Initializes the grid query with a default query since the user hasn't selected a query.
        /// 
        /// The default query consists of a single clause to restrict the query to the selected team project
        /// with only System.Id as the projected value
        /// </summary>
        private void SetDefaultQuery(string teamProjectName)
        {
            SetQuery("select [System.Id] from WorkItem where [System.TeamProject] = @project",_store,teamProjectName);
        }

        /// <summary>
        /// Sets the query type on the query type combo box
        /// </summary>
        /// <param name="queryType"></param>
        private void SetQueryType(QueryType queryType)
        {
            switch (queryType)
            {
                case QueryType.List: queryTypeComboBox.SelectedIndex = 0; 
                    break;
                case QueryType.OneHop: queryTypeComboBox.SelectedIndex = 1;
                    break;
                case QueryType.Tree: queryTypeComboBox.SelectedIndex = 2;
                    break;
                default: throw new NotImplementedException("unknown query type");
            }
        }

        /// <summary>
        /// generates a query according with the query that has been designed.
        /// 
        /// If the query is invalid it throws an exception
        /// </summary>
        /// <returns></returns>
        public string GenerateWIQL(string teamProjectName)
        {
            ResultOptions resultOptions;
            string wiql;
            string error;

            resultOptions = new ResultOptions(_displayFields, TransformListToCollection(_sortFieldList));

            if (queryBuilderControl.FilterIsValid(out error) == false)
            {
                throw new Exception("Invalid Query " + error);
            }

            wiql = queryBuilderControl.GenerateWiql(resultOptions);

            ThrowExceptionIfWIQLNotValid(wiql, teamProjectName);

            return wiql;
        }

        /// <summary>
        /// Throws an exception if the query is not valid.
        /// </summary>
        /// <param name="wiql"></param>
        private void ThrowExceptionIfWIQLNotValid(string wiql, string teamProjectName)
        {
            Query query;

            query = new Query(_store, wiql, GetWIQLContext(teamProjectName));

            //In theory if there is an error in the query the ctor has thrown it. However we will 
            //do a double check just in case.

            //Validate the projection fields 
            foreach (FieldDefinition displayField in query.DisplayFieldList)
            {
                if (_store.FieldDefinitions.Contains(displayField) == false)
                {
                    throw new Exception("Invalid Query. Unknown Field " + displayField.Name);
                }
            }

            //Valid the sort fields
            foreach (Microsoft.TeamFoundation.WorkItemTracking.Client.SortField sortField in query.SortFieldList)
            {
                if (_store.FieldDefinitions.Contains(sortField.FieldDefinition) == false)
                {
                    throw new Exception("Invalid Query. Unknown Field " + sortField.FieldDefinition.Name);
                }
            }
        }

        #region helpers
        private DisplayFieldCollection TransformListIntoCollection(DisplayFieldList displayFieldList)
        {
            DisplayFieldCollection collection;

            collection = new DisplayFieldCollection();
            foreach (FieldDefinition field in displayFieldList)
            {
                collection.Add(new DisplayField(field.ReferenceName, 30));
            }

            return collection;
        }

        private SortFieldCollection TransformListToCollection(SortFieldList sortFieldList)
        {
            SortFieldCollection collection;

            if (sortFieldList == null) return null;

            collection = new SortFieldCollection();
            foreach (Microsoft.TeamFoundation.WorkItemTracking.Client.SortField field in sortFieldList)
            {
                collection.Add(new Microsoft.TeamFoundation.WorkItemTracking.Controls.SortField(field.FieldDefinition.ReferenceName, field.SortType == SortType.Ascending));
            }

            return collection;
        }
        #endregion

        /// <summary>
        /// Gets the context menu to be used on the query editor grid when the user right clicks
        /// </summary>
        /// <returns></returns>
        private System.Windows.Forms.ContextMenu GenerateContextMenu()
        {
            ContextMenu contextMenu;

            contextMenu = new ContextMenu();

            contextMenu.MenuItems.Add(new MenuItem("Insert Clause", (s, e) => { InsertClause(); }));
            contextMenu.MenuItems.Add(new MenuItem("Delete Clause(s)", (s, e) => { DeleteSelectedClauses(); }));
            contextMenu.MenuItems.Add(new MenuItem("Group Clause(s)", (s, e) => { GroupSelectedClauses(); }));
            contextMenu.MenuItems.Add(new MenuItem("Ungroup Clause(s)", (s, e) => { UngroupSelectedClauses(); }));

            return contextMenu;
        }        

        /// <summary>
        /// Updates the toolstrips enabled/disabled status depending on the operations that can be currently done
        /// according to the what is selected in the query grid
        /// 
        /// This will enable or disable the operations that can be performed on the toolbar depending on the selections
        /// that are made on the grid itself (eg: a column is selected)
        /// </summary>
        private void UpdateToolStripButtons()
        {
            toolStripDeleteClauseButton.Enabled = queryBuilderControl.CanDeleteSelectedCaluses;
            toolStripGroupClauseButton.Enabled = queryBuilderControl.CanGroupSelectedCaluses;
            toolStripUngroupClauseButton.Enabled = queryBuilderControl.CanUngroupSelectedCaluses;
        }        

        /// <summary>
        /// Deletes the clauses that are graphically selected on the query editor
        /// </summary>
        private void DeleteSelectedClauses()
        {
            queryBuilderControl.DeleteSelectedClauses();
            UpdateToolStripButtons();
        }

        /// <summary>
        /// Groups the clauses that are graphically selected on the query editor
        /// </summary>
        private void GroupSelectedClauses()
        {
            queryBuilderControl.GroupSelectedClauses();
            UpdateToolStripButtons();
        }

        /// <summary>
        /// removes the grouping of the clauses that are grouped and visually selected on the query editor
        /// </summary>
        private void UngroupSelectedClauses()
        {
            queryBuilderControl.UngroupSelectedClauses();
            UpdateToolStripButtons();
        }

        /// <summary>
        /// Inserts a new clause (where the cursor is placed)
        /// </summary>
        private void InsertClause()
        {
            queryBuilderControl.InsertClause();
            UpdateToolStripButtons();
        }

        #region events

        protected override void OnLeave(EventArgs e)
        {
            base.OnLeave(e);

            if (_toolbarImageList != null)
                _toolbarImageList.Dispose();
        }


        private void toolStripGroupClauseButton_Click_1(object sender, EventArgs e)
        {
            GroupSelectedClauses();
        }

        private void toolStripUngroupClauseButton_Click_1(object sender, EventArgs e)
        {
            UngroupSelectedClauses();
        }

        private void toolStripDeleteClauseButton_Click_1(object sender, EventArgs e)
        {
            DeleteSelectedClauses();
        }

        /// <summary>
        /// The user has selected the execution of the query.
        /// 
        /// If there are event subscribers we fire the QueryRunRequested event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void toolStripRunButton_Click_1(object sender, EventArgs e)
        {
            if (QueryRunRequested != null)
            {
                string wiql;

                try
                {
                    wiql = GenerateWIQL(_teamProjectName);
                }
                catch (Exception exception)
                {
                    MessageBox.Show("The query has an error: " + exception.Message,"Error",MessageBoxButtons.OK,MessageBoxIcon.Error);
                    return;
                }

                try
                {
                    QueryRunRequested(this, new QueryRunEventArgs { WIQL = wiql, QueryType = queryBuilderControl.QueryType });
                }
                catch (Exception exception)
                {
                    MessageBox.Show(exception.Message,"Error",MessageBoxButtons.OK,MessageBoxIcon.Error);
                    return;
                }
            }
        }

        /// <summary>
        /// Shows the context menu.
        /// 
        /// The options on the menu are enabled or disabled based on the clauses that are visually
        /// checked by the user
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void queryBuilderControl_ContextMenuPopup(object sender, MouseEventArgs e)
        {
            QueryBuilderControl queryBuilder;

            queryBuilder = sender as QueryBuilderControl;

            Debug.Assert(queryBuilder != null);

            queryBuilder.ContextMenu.MenuItems[1].Enabled = queryBuilder.CanDeleteSelectedCaluses;
            queryBuilder.ContextMenu.MenuItems[2].Enabled = queryBuilder.CanGroupSelectedCaluses;
            queryBuilder.ContextMenu.MenuItems[3].Enabled = queryBuilder.CanUngroupSelectedCaluses;

            queryBuilderControl.ContextMenu.Show((Control)sender, e.Location);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void queryBuilderControl_ClauseFocusChanged(object sender, EventArgs e)
        {
            UpdateToolStripButtons();
        }

        private void queryTypeComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            switch (queryTypeComboBox.SelectedIndex)
            {
                case 0: queryBuilderControl.QueryType = QueryType.List; break;
                case 1: queryBuilderControl.QueryType = QueryType.OneHop; break;
                case 2: queryBuilderControl.QueryType = QueryType.Tree; break;
                default: throw new NotImplementedException("unknown query type");
            }
        }

        private void toolStripDeleteClauseButton_Click(object sender, EventArgs e)
        {
            DeleteSelectedClauses();
        }

        private void toolStripUngroupClauseButton_Click(object sender, EventArgs e)
        {
            UngroupSelectedClauses();
        }

      
        #endregion

        /// <summary>
        /// Event triggered when the user selects the column options 
        /// 
        /// Opens the form that allows the user to select the columns that appear in the query
        /// (and the sort fields)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void toolStripColumnOptionsButton_Click_1(object sender, EventArgs e)
        {
            ColumnOptionsForm optionsForm = new ColumnOptionsForm(_store, _displayFields, _sortFieldList);

            optionsForm.TeamProject = _teamProjectName;


            if (optionsForm.ShowDialog() == DialogResult.OK)
            {
                _displayFields = optionsForm.DisplayFields;
                _sortFieldList = optionsForm.SortFields;
            }
            optionsForm.Close();
        }
    }
}
